package com.mach.platform.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.*;
import com.mach.platform.domain.AlarmMessage;
import com.mach.platform.domain.iot.IotDevParams;
import com.mach.platform.repository.AlarmMessageRepo;
import com.mach.platform.repository.IotDevParamsRepo;
import com.mach.platform.repository.IotDevRepo;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 报警预测逻辑
 */

@Service
public class AlarmServiceImpl{

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    public final static long spaceId = 295;
    public static volatile boolean nowAlarmType = false;
    public static List<Map> CUR_DATE_LIST = new ArrayList<>(10000);
    public static ConcurrentHashMap<Integer, List<Map>> concurrentHashMap = new ConcurrentHashMap<>(1024);
    private static final int INITIAL_CAPACITY = 128;//concurrentHashMap里面value List的初始容量
    public static Map CUR_MAP  = Maps.newHashMap();

    private final static String underline = "_";
    private final static long resetTime = 3600 * 1000;
    private static long clearMapTime = 0;
    private final static long cacheTime = 3600 * 1000;
    private final static Map<String, LocalDateTime> MAP = Maps.newHashMap();

    private final static String UPPER_LIMIT = "上限";
    private final static String LOWER_LIMIT = "下限";
    private final static String TIME_FRAME = "时间范围";
    private final static String TD_RISE = "温差上涨";
    private final static String TEMP_RANGE = "温度幅度";
    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

    @Autowired
    private IotDevRepo deviceRepo;

    @Autowired
    private IotDevParamsRepo iotDevParamsRepo;

    @Autowired
    private AlarmMessageRepo alarmMessageRepo;

    public void init(){

        try {
            StopWatch sw = new StopWatch();
            sw.start();

            //TODO 现在是查询母排设备，以后要获取全部系统的所有设备进行计算
            //母排所有设备
            List<Map> iotDevList = deviceRepo.findAllByIotSpace(spaceId, -1);

            submitCache(iotDevList);
            handleDevData(iotDevList);
            sw.stop();
            String tip = String.format("所用时间：[%s]毫秒", sw.getTime());
            System.out.println(tip);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void submitCache(List<Map> iotDevList) {
        //缓存cur表数据
        cacheCur(iotDevList);
        //清除缓存中时间小于一小时的数据
        try {
            delCacheCur(CUR_DATE_LIST, 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void cacheCur(List<Map> iotDevList) {
        try {
            for (Map map : iotDevList) {
                String mapKey = getMapKey(map);
                Object time = map.get("the_time");
                Object time1 = CUR_MAP.get(mapKey);
                boolean isAdd = true;
                if (Objects.equals(time, time1)){
                    isAdd = false;
                }
                if (isAdd){
                    CUR_DATE_LIST.add(map);
                    CUR_MAP.put(mapKey, time);
                    put(map);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void put(Map map) {
        List<Map> maps = concurrentHashMap.get(Integer.parseInt(map.get("id").toString()));
        if (CollectionUtils.isNotEmpty(maps)) {
            maps.add(map);
            concurrentHashMap.put(Integer.parseInt(map.get("id").toString()), maps);
        }else{
            List<Map> newList = new ArrayList<>(INITIAL_CAPACITY);
            newList.add(map);
            concurrentHashMap.put(Integer.parseInt(map.get("id").toString()), newList);
        }
    }

    private String getMapKey(Map map) {
        if (!Objects.isNull(map)){
            return map.get("id") + underline + map.get("data_type");
        }
        return null;
    }

    private void delCacheCur(List<Map> curDate, double hourNum) {

        clearService(hourNum, curDate);
        if (System.currentTimeMillis() - clearMapTime > cacheTime){
            concurrentHashMap.forEach((k, v) ->{
                List<Map> maps = v;
                clearMap(maps);
            });
            clearMapTime = System.currentTimeMillis();
        }
    }

    private void clearService(double hourNum, List<Map> curDate) {
        Iterator<Map> mapIterator = curDate.iterator();
        while (mapIterator.hasNext()){
            Map itemMap = mapIterator.next();
            try {
                String time = itemMap.get("the_time").toString();
                Date date = fmt.parse(time);      //将从数据库读出来的 timestamp 类型的时间转换为java的Date类型
                if (System.currentTimeMillis() - date.getTime() > cacheTime * hourNum){
                    mapIterator.remove();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void clearMap(List<Map> maps) {
        if (CollectionUtils.isNotEmpty(maps)){
            clearService(0.5, maps);
        }
    }


    private void handleDevData(List<Map> iotDevList) {
        //按devId分组
        Map<Object, List<Map>> groupDevByDevId = iotDevList.stream().collect(Collectors.groupingBy((Map map) -> map.get("id")));

        //prop表
        List<IotDevParams> propMapList = this.propList(iotDevList);

        //按devId分组
        Map<Object, List<IotDevParams>> groupPropByDevId = propMapList.stream().collect(Collectors.groupingBy(IotDevParams::getDevId));


        //开始比较上下限
        compareBound(groupDevByDevId, groupPropByDevId);


        //开始比较温差
        compareTD(groupDevByDevId, groupPropByDevId);

        //超线报警
        superlineAlarm(groupPropByDevId);

    }



    private void compareBound(Map<Object, List<Map>> groupDevByDevId, Map<Object, List<IotDevParams>> groupPropByDevId) {


        if (MapUtils.isEmpty(groupDevByDevId) && MapUtils.isEmpty(groupPropByDevId)){
            logger.info("设备为空或者设备参数为空");
        }

        for (Map.Entry entryDev : groupDevByDevId.entrySet()){
            Integer devId = Integer.parseInt(entryDev.getKey().toString());
            List<Map> curData = (List<Map>) entryDev.getValue();
            List<IotDevParams> propData = groupPropByDevId.get(devId);
            Map itemResult = this.compareData(curData, propData, true);//比较数据
        }
    }

    private Map delNormalType(Map itemResult) {
        itemResult.forEach((k, v) -> {
            if (Integer.parseInt(v.toString()) == 0){
                itemResult.remove(k);
            }
        });
        return itemResult;
    }

    /**
     * 通过cur表data_type过滤prop表的上下限
     * @param curData cur数据
     * @param propData
     * @param ifInsert
     */
    public Map compareData(List<Map> curData, List<IotDevParams> propData, boolean ifInsert) {
        String switchNumber = this.getSwitchNumber(propData);
        String busNumber = this.getBusNumber(propData);
        Map result = Maps.newHashMap();
        for (Map curMap : curData){
            String dataType = curMap.get("data_type").toString();
            //label过滤
            List<IotDevParams> filterPropByLabel = null;
            try {
                filterPropByLabel = propData.stream().filter(p -> filterByLabel(dataType, p)).collect(Collectors.toList());
            } catch (Exception e) {
                e.printStackTrace();
            }
            int compareResult = this.compareStart(curMap, filterPropByLabel);
            if (ifInsert) {
                this.ifInsertDB(curMap, compareResult, switchNumber, busNumber);
            }
            result.put(dataType, compareResult);
        }
        return result;
    }

    private boolean filterByLabel(String dataType, IotDevParams p) {
        return p.getLabel().equals(dataType + UPPER_LIMIT) || p.getLabel().equals(dataType + LOWER_LIMIT);
    }

    /**
     * 比较上下限
     * @param curMap value集
     * @param filterPropByLabel 上限和下限集
     */
    private int compareStart(Map curMap, List<IotDevParams> filterPropByLabel) {
        if (CollectionUtils.isEmpty(filterPropByLabel)){
            logger.info("找不到配置信息 ==> iot_dev_prop，devId：" + curMap.get("id"));
            return 0;
        }
        //比较值
        double dvalue = Double.parseDouble(curMap.get("dvalue").toString());
        double up = 0;
        double lower = 0;
        for (IotDevParams iotDevParams : filterPropByLabel){
            //获取上限
            if (iotDevParams.getLabel().contains(UPPER_LIMIT)){
                up = iotDevParams.getValue();
            }
            //获取下限
            if (iotDevParams.getLabel().contains(LOWER_LIMIT)){
                lower = iotDevParams.getValue();
            }
        }
        int compareNumber = 0;
        if (up != 0 && lower != 0){
            compareNumber = this.compareNumber(dvalue, up, lower);
        }
        return compareNumber;
    }

    /**
     * 比较上下限
     * @param dvalue 值
     * @param up 上限
     * @param lower 下限
     * @return {0：正常 1：超上限 -1：低下限}
     */
    private int compareNumber(double dvalue, double up, double lower) {
        if (dvalue > up){
            return 1;
        }
        if (dvalue < lower){
            return -1;
        }
        return 0;
    }

    private void ifInsertDB(Map curMap, int compareNumber, String switchNumber, String busNumber) {
        //超上限
        if (compareNumber > 0){
            this.insertAlarmData(curMap, "上限报警", switchNumber, busNumber);
        }

        //低下限
        if (compareNumber < 0){
            this.insertAlarmData(curMap, "下限报警", switchNumber, busNumber);
        }
    }


    private void compareTD(Map<Object, List<Map>> groupDevByDevId, Map<Object, List<IotDevParams>> groupPropByDevId) {
        for (Map.Entry entryDev : groupDevByDevId.entrySet()){
            Integer devId = Integer.parseInt(entryDev.getKey().toString());
            List<IotDevParams> propData = groupPropByDevId.get(devId);//根据id获取属性配置
            List<Map> curData = (List<Map>) entryDev.getValue();//a,b,c温度的项

            compareTDByItem(curData, propData, true);
        }
    }


    /**
     * 温差报警
     * @param curData curData数据
     * @param propData 参数属性数据
     * @param ifInsert 是否插入
     * @return
     */
    public Map compareTDByItem(List<Map> curData, List<IotDevParams> propData, boolean ifInsert) {
        if (CollectionUtils.isEmpty(curData) || CollectionUtils.isEmpty(propData)){
            return Collections.emptyMap();
        }
        String switchNumber = this.getSwitchNumber(propData);
        String busNumber = this.getBusNumber(propData);
        Map resultMap = Maps.newHashMap();
        List<Double> doubles = Lists.newArrayList();//a,b,c温度的值
        for (Map curMap : curData){
            doubles.add(Double.parseDouble(curMap.get("dvalue").toString()));
        }

        Map curMap = this.maxValue(curData);

        double result = this.compareTDData(doubles);
        int compareResult = this.compareTDStart(result, propData);
        if (compareResult > 0 && ifInsert){
            this.insertAlarmData(curMap, "温差报警", switchNumber, busNumber);
        }
        resultMap.put(curMap.get("data_type"), compareResult);
        return resultMap;
    }

    private Map maxValue(List<Map> curData) {
        double maxValue = 0;
        Map result = null;
        for (Map map : curData){
            double value = Double.parseDouble(map.get("dvalue").toString());
            if (value > maxValue){
                maxValue = value;
                result = map;
            }
        }
        return result;
    }

    private double compareTDData(List<Double> doubles) {
        Collections.sort(doubles);
        return doubles.size() > 0 ? doubles.get(doubles.size() - 1) - doubles.get(0) : 0;
    }

    private int compareTDStart(double result, List<IotDevParams> propData) {
        //name过滤
        List<IotDevParams> filterPropByName = null;
        try {
            filterPropByName = propData.stream().filter(p -> Objects.equals(p.getLabel(), TEMP_RANGE)).collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (CollectionUtils.isEmpty(filterPropByName)){
            logger.info("找不到配置信息 ==> iot_dev_prop");
            return 0;
        }
        double value = filterPropByName.get(0).getValue();
        //温度差大于幅度
        if (result > value){
            return 1;
        }
        return 0;
    }





    private void superlineAlarm(Map<Object, List<IotDevParams>> groupPropByDevId) {
        /**
         * 一次性查出win母排所有数据，再根据prop表设定的时间取出时间段
         */
        //母排所有设备 win表数据
        Map<Integer, List<Map>> groupWinByDevId = concurrentHashMap;
        this.extractData(groupPropByDevId, groupWinByDevId);
    }

    /**
     * 提取数据
     * @param groupPropByDevId 配置信息
     * @param groupWinByDevId 数据来源
     */
    private void extractData(Map<Object, List<IotDevParams>> groupPropByDevId, Map<Integer, List<Map>> groupWinByDevId) {
        if(MapUtils.isEmpty(groupPropByDevId) && MapUtils.isEmpty(groupWinByDevId)){
            logger.info("配置信息为空或者数据为空，不操作");
            return;
        }
        for (Map.Entry entryWin : groupWinByDevId.entrySet()){
            Integer devId = Integer.parseInt(entryWin.getKey().toString());
            List<Map> winData = (List<Map>) entryWin.getValue();//数据
            List<IotDevParams> iotDevParams = groupPropByDevId.get(devId);//配置
            extractDataByMap(winData, iotDevParams, true);
        }
    }


    /**
     * 超限报警
     * @param winData win表数据
     * @param iotDevParams 参数设定数据
     * @param ifInsert 是否插入
     * @return
     */
    public Map extractDataByMap(List<Map> winData, List<IotDevParams> iotDevParams, boolean ifInsert) {
        //切分数据
        Map<IotDevParams, List<Map>> segmentationData = this.segmentationData(winData, iotDevParams);
        //比较数据
        return this.compareExtractData(segmentationData, iotDevParams, ifInsert);
    }

    private Map compareExtractData(Map<IotDevParams, List<Map>> segmentationData, List<IotDevParams> iotDevParams, boolean ifInsert) {
        Map result = Maps.newHashMap();
        String switchNumber = this.getSwitchNumber(iotDevParams);
        String busNumber = this.getBusNumber(iotDevParams);
        for (Map.Entry entry : segmentationData.entrySet()){
            List<Map> maps = (List<Map>) entry.getValue();
            if (CollectionUtils.isEmpty(maps) && maps.size() < 2){
                //logger.info("设备在win表数据为空或者数据小于2，没有可比性");
                continue;
            }
            double maxValue = Double.parseDouble(maps.get(0).get("dvalue").toString());
            double minValue = maxValue;
            IotDevParams iotDevParam = (IotDevParams) entry.getKey();
            double value = iotDevParam.getValue();
            for (Map winItem : maps){
                String davlue = winItem.get("dvalue").toString();
                if (maxValue < Double.parseDouble(davlue)){
                    maxValue = Double.parseDouble(davlue);
                }
                if (minValue > Double.parseDouble(davlue)){
                    minValue = Double.parseDouble(davlue);
                }
            }
            if (maxValue != 0 && minValue != 0 && value != 0 && ifInsert){
                if ((maxValue - minValue) > value){
                    result.put(maps.get(0), 1);
                    this.insertAlarmData(maps.get(0), "超线报警", switchNumber, busNumber);
                }else{
                    result.put(maps.get(0), 0);
                }
            }
        }
        return result;
    }

    /**
     * 获取母线编号
     * @param iotDevParams
     * @return
     */
    private String getBusNumber(List<IotDevParams> iotDevParams) {
        if (CollectionUtils.isEmpty(iotDevParams)){
            return null;
        }
        for (IotDevParams iotDevParam : iotDevParams){
            if (iotDevParam.getName().equals("busNumber")){
                return iotDevParam.getTextValue();
            }
        }
        return null;
    }

    /**
     * 获取开关编号
     * @param iotDevParams
     * @return
     */
    private String getSwitchNumber(List<IotDevParams> iotDevParams) {
        if (CollectionUtils.isEmpty(iotDevParams)){
            return null;
        }
        for (IotDevParams iotDevParam : iotDevParams){
            if (iotDevParam.getName().equals("switchNumber")){
                return iotDevParam.getTextValue();
            }
        }
        return null;
    }

    private Map<IotDevParams, List<Map>> segmentationData(List<Map> winData, List<IotDevParams> iotDevParams) {
        Map<IotDevParams, List<Map>> result = Maps.newHashMap();
        if (CollectionUtils.isEmpty(winData) || CollectionUtils.isEmpty(iotDevParams)){
            //logger.info("devId:" + winData.get(0).get("id").toString());
            return result;
        }
        //按dataType分组 出来A温度,b温度,c温度
        Map<Object, List<Map>> groupWinByDataType = winData.stream().collect(Collectors.groupingBy((Map map) -> map.get("data_type")));
        for (IotDevParams iotDevParam : iotDevParams){

            for (Map.Entry entry : groupWinByDataType.entrySet()){
                if (Objects.equals(entry.getKey().toString() + TIME_FRAME, iotDevParam.getLabel())){
                    List<Map> winDataType = (List<Map>) entry.getValue();
                    List<Map> resultC = this.finalData(iotDevParam, winDataType);
                    IotDevParams mapKey = getIotDevParams(iotDevParams, entry);
                    result.put(mapKey, resultC);
                    break;
                }
            }
        }
        return result;
    }

    private IotDevParams getIotDevParams(List<IotDevParams> iotDevParams, Map.Entry entry) {
        IotDevParams mapKey = null;
        for (IotDevParams iotDevParam1 : iotDevParams){
            if (Objects.equals(entry.getKey().toString() + TD_RISE, iotDevParam1.getLabel())){
                mapKey = iotDevParam1;
                break;
            }
        }
        return mapKey;
    }

    private List<Map> finalData(IotDevParams iotDevParam, List<Map> winDataType) {
        SimpleDateFormat myFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<Map> result = Lists.newArrayList();
        if (CollectionUtils.isEmpty(winDataType)){
            return result;
        }
        long nowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        for (Map mapList : winDataType){
            String time = mapList.get("the_time").toString();
            if (StringUtils.isEmpty(time)){
                continue;
            }
            Date date = null;
            try {
                date = myFmt.parse(time);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                long diffTime = nowTime - date.toInstant()
                        .atZone(ZoneId.systemDefault())
                        .toLocalDateTime().toEpochSecond(ZoneOffset.of("+8"));
                if (iotDevParam.getValue() < diffTime){
                    break;
                }
                result.add(mapList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }




    /**
     * 插入报警信息
     * @param curMap 报警基本信息
     * @param classify 报警分类
     * @param switchNumber 开关编号
     * @param busNumber 母线编号
     */
    private void insertAlarmData(Map curMap, String classify, String switchNumber, String busNumber) {

        /**
         * 判断是否需要插入到数据库
         * 一个小时插入一次
         * @return false:插入 true:不插入
         */
        if(judgeTimeIfInsertDb(curMap, classify)) return;

        if (judgeIfInsertDb(curMap)) return;

        AlarmMessage alarmMessage = new AlarmMessage();
        alarmMessage.setAlarmClassify(classify);
        alarmMessage.setAlarmMessageTime(LocalDateTime.now());
        alarmMessage.setAlarmTerm(curMap.get("data_type").toString());
        alarmMessage.setAlarmValue(Double.parseDouble(curMap.get("dvalue").toString()));
        alarmMessage.setAlarmWorkshop(curMap.get("workshopName").toString());
        alarmMessage.setBusNumber(busNumber);
        alarmMessage.setIsOperation(0);
        alarmMessage.setSwitchNumber(switchNumber);
        alarmMessage.setWorkshopId(Integer.parseInt(curMap.get("workshopId").toString()));
        alarmMessage.setDevName(curMap.get("name").toString());

        this.alarmMessageRepo.save(alarmMessage);
        MAP.put(getMapKey(curMap, classify), LocalDateTime.now());
    }

    private boolean judgeTimeIfInsertDb(Map curMap, String classify) {
        String mapKey = getMapKey(curMap, classify);
        LocalDateTime dateTime = MAP.get(mapKey);
        /**
         * 如果为空则是第一次插入
         */
        if (Objects.isNull(dateTime)){
            return false;
        }
        //获取毫秒数
        long milliSecond = dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        //获取毫秒数
        if (System.currentTimeMillis() - milliSecond < resetTime){
            return true;
        }
        return false;
    }

    private String getMapKey(Map curMap, String classify) {
        return classify + underline + curMap.get("data_type") + underline + curMap.get("name") + curMap.get("id");
    }

    /**
     * 判断是否需要插入数据库
     * @param curMap
     * @return
     */
    private boolean judgeIfInsertDb(Map curMap) {
        boolean ifReSet = this.judgeIfReSet(curMap);
        if (!ifReSet){
            return true;
        }
        if (!nowAlarmType){
            AlarmServiceForeignImpl.isAlarm = 1;
            logger.info("设备属性在复位中，不插入数据库");
            return true;
        }
        AlarmServiceForeignImpl.isAlarm = 0;

        return false;
    }

    /**
     * 判断复位时间是否小于一小时
     * @param curMap
     * @return
     */
    private boolean judgeIfReSet(Map curMap) {
        String devId = curMap.get("id").toString();
        String dataType = curMap.get("data_type").toString();
        String idTypeKey = devId + underline + dataType;
        LocalDateTime dateTime = (LocalDateTime) AlarmServiceForeignImpl.resetMap.get(idTypeKey);
        // 时间为空说明复位内存中没有该属性或者新的报警属性
        if (!ObjectUtils.allNotNull(dateTime)){
            nowAlarmType = true;
            return true;
        }
        //获取毫秒数
        long milliSecond = dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (System.currentTimeMillis() - milliSecond < resetTime){
            return false;
        }
        return true;
    }





    /**
     * 通过结果集获取设备id，通过设备id查询iot_dev_prop
     * @param iotDevList
     * @return
     */
    private List<IotDevParams> propList(List<Map> iotDevList) {

        Set<Integer> devIdSet = Sets.newHashSet();
        for (Map map : iotDevList){
            devIdSet.add(Integer.parseInt(map.get("id").toString()));
        }
        List<Integer> devIdList = new ArrayList<>(devIdSet);

        List<IotDevParams> iotDevParams = iotDevParamsRepo.findAllByDevIdIn(devIdList);
        return iotDevParams;
    }

}
